1   /*
2    * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.security.jca;
27  
28  import java.io.File;
29  import java.lang.reflect.*;
30  
31  import java.security.*;
32  
33  import sun.security.util.PropertyExpander;
34  
35  /**
36   * Class representing a configured provider. Encapsulates configuration
37   * (className plus optional argument), the provider loading logic, and
38   * the loaded Provider object itself.
39   *
40   * @author  Andreas Sterbenz
41   * @since   1.5
42   */
43  final class ProviderConfig {
44  
45      private final static sun.security.util.Debug debug =
46          sun.security.util.Debug.getInstance("jca", "ProviderConfig");
47  
48      // classname of the SunPKCS11-Solaris provider
49      private static final String P11_SOL_NAME =
50          "sun.security.pkcs11.SunPKCS11";
51  
52      // config file argument of the SunPKCS11-Solaris provider
53      private static final String P11_SOL_ARG  =
54          "${java.home}/lib/security/sunpkcs11-solaris.cfg";
55  
56      // maximum number of times to try loading a provider before giving up
57      private final static int MAX_LOAD_TRIES = 30;
58  
59      // parameters for the Provider(String) constructor,
60      // use by doLoadProvider()
61      private final static Class[] CL_STRING = { String.class };
62  
63      // name of the provider class
64      private final String className;
65  
66      // argument to the provider constructor,
67      // empty string indicates no-arg constructor
68      private final String argument;
69  
70      // number of times we have already tried to load this provider
71      private int tries;
72  
73      // Provider object, if loaded
74      private volatile Provider provider;
75  
76      // flag indicating if we are currently trying to load the provider
77      // used to detect recursion
78      private boolean isLoading;
79  
80      ProviderConfig(String className, String argument) {
81          if (className.equals(P11_SOL_NAME) && argument.equals(P11_SOL_ARG)) {
82              checkSunPKCS11Solaris();
83          }
84          this.className = className;
85          this.argument = expand(argument);
86      }
87  
88      ProviderConfig(String className) {
89          this(className, "");
90      }
91  
92      ProviderConfig(Provider provider) {
93          this.className = provider.getClass().getName();
94          this.argument = "";
95          this.provider = provider;
96      }
97  
98      // check if we should try to load the SunPKCS11-Solaris provider
99      // avoid if not available (pre Solaris 10) to reduce startup time
100     // or if disabled via system property
101     private void checkSunPKCS11Solaris() {
102         Boolean o = AccessController.doPrivileged(
103                                 new PrivilegedAction<Boolean>() {
104             public Boolean run() {
105                 File file = new File("/usr/lib/libpkcs11.so");
106                 if (file.exists() == false) {
107                     return Boolean.FALSE;
108                 }
109                 if ("false".equalsIgnoreCase(System.getProperty
110                         ("sun.security.pkcs11.enable-solaris"))) {
111                     return Boolean.FALSE;
112                 }
113                 return Boolean.TRUE;
114             }
115         });
116         if (o == Boolean.FALSE) {
117             tries = MAX_LOAD_TRIES;
118         }
119     }
120 
121     private boolean hasArgument() {
122         return argument.length() != 0;
123     }
124 
125     // should we try to load this provider?
126     private boolean shouldLoad() {
127         return (tries < MAX_LOAD_TRIES);
128     }
129 
130     // do not try to load this provider again
131     private void disableLoad() {
132         tries = MAX_LOAD_TRIES;
133     }
134 
135     boolean isLoaded() {
136         return (provider != null);
137     }
138 
139     public boolean equals(Object obj) {
140         if (this == obj) {
141             return true;
142         }
143         if (obj instanceof ProviderConfig == false) {
144             return false;
145         }
146         ProviderConfig other = (ProviderConfig)obj;
147         return this.className.equals(other.className)
148             && this.argument.equals(other.argument);
149     }
150 
151     public int hashCode() {
152         return className.hashCode() + argument.hashCode();
153     }
154 
155     public String toString() {
156         if (hasArgument()) {
157             return className + "('" + argument + "')";
158         } else {
159             return className;
160         }
161     }
162 
163     /**
164      * Get the provider object. Loads the provider if it is not already loaded.
165      */
166     synchronized Provider getProvider() {
167         // volatile variable load
168         Provider p = provider;
169         if (p != null) {
170             return p;
171         }
172         if (shouldLoad() == false) {
173             return null;
174         }
175         if (isLoading) {
176             // because this method is synchronized, this can only
177             // happen if there is recursion.
178             if (debug != null) {
179                 debug.println("Recursion loading provider: " + this);
180                 new Exception("Call trace").printStackTrace();
181             }
182             return null;
183         }
184         try {
185             isLoading = true;
186             tries++;
187             p = doLoadProvider();
188         } finally {
189             isLoading = false;
190         }
191         provider = p;
192         return p;
193     }
194 
195     /**
196      * Load and instantiate the Provider described by this class.
197      *
198      * NOTE use of doPrivileged().
199      *
200      * @return null if the Provider could not be loaded
201      *
202      * @throws ProviderException if executing the Provider's constructor
203      * throws a ProviderException. All other Exceptions are ignored.
204      */
205     private Provider doLoadProvider() {
206         return AccessController.doPrivileged(new PrivilegedAction<Provider>() {
207             public Provider run() {
208                 if (debug != null) {
209                     debug.println("Loading provider: " + ProviderConfig.this);
210                 }
211                 try {
212                     ClassLoader cl = ClassLoader.getSystemClassLoader();
213                     Class<?> provClass;
214                     if (cl != null) {
215                         provClass = cl.loadClass(className);
216                     } else {
217                         provClass = Class.forName(className);
218                     }
219                     Object obj;
220                     if (hasArgument() == false) {
221                         obj = provClass.newInstance();
222                     } else {
223                         Constructor<?> cons = provClass.getConstructor(CL_STRING);
224                         obj = cons.newInstance(argument);
225                     }
226                     if (obj instanceof Provider) {
227                         if (debug != null) {
228                             debug.println("Loaded provider " + obj);
229                         }
230                         return (Provider)obj;
231                     } else {
232                         if (debug != null) {
233                             debug.println(className + " is not a provider");
234                         }
235                         disableLoad();
236                         return null;
237                     }
238                 } catch (Exception e) {
239                     Throwable t;
240                     if (e instanceof InvocationTargetException) {
241                         t = ((InvocationTargetException)e).getCause();
242                     } else {
243                         t = e;
244                     }
245                     if (debug != null) {
246                         debug.println("Error loading provider " + ProviderConfig.this);
247                         t.printStackTrace();
248                     }
249                     // provider indicates fatal error, pass through exception
250                     if (t instanceof ProviderException) {
251                         throw (ProviderException)t;
252                     }
253                     // provider indicates that loading should not be retried
254                     if (t instanceof UnsupportedOperationException) {
255                         disableLoad();
256                     }
257                     return null;
258                 }
259             }
260         });
261     }
262 
263     /**
264      * Perform property expansion of the provider value.
265      *
266      * NOTE use of doPrivileged().
267      */
268     private static String expand(final String value) {
269         // shortcut if value does not contain any properties
270         if (value.contains("${") == false) {
271             return value;
272         }
273         return AccessController.doPrivileged(new PrivilegedAction<String>() {
274             public String run() {
275                 try {
276                     return PropertyExpander.expand(value);
277                 } catch (GeneralSecurityException e) {
278                     throw new ProviderException(e);
279                 }
280             }
281         });
282     }
283 
284 }